package net.sunofbeach.blog.services.impl;

import com.google.gson.Gson;
import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import net.sunofbeach.blog.dao.RefreshTokenDao;
import net.sunofbeach.blog.dao.SettingDao;
import net.sunofbeach.blog.dao.UserDao;
import net.sunofbeach.blog.dao.UserNoPasswordDao;
import net.sunofbeach.blog.pojo.RefreshToken;
import net.sunofbeach.blog.pojo.Setting;
import net.sunofbeach.blog.pojo.SobUser;
import net.sunofbeach.blog.pojo.SobUserNoPassword;
import net.sunofbeach.blog.response.ResponseResult;
import net.sunofbeach.blog.response.ResponseState;
import net.sunofbeach.blog.services.IUserService;
import net.sunofbeach.blog.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@Transactional
public class UserServiceImpl extends BaseService implements IUserService {

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private UserDao userDao;

    @Autowired
    private SettingDao settingsDao;

    @Autowired
    private RefreshTokenDao refreshTokenDao;

    @Autowired
    private Gson gson;


    @Override
    public ResponseResult initManagerAccount(SobUser sobUser, HttpServletRequest request) {
        //检查是否有初始化
        Setting managerAccountState = settingsDao.findOneByKey(Constants.Settings.MANAGER_ACCOUNT_INIT_STATE);
        if (managerAccountState != null) {
            return ResponseResult.FAILED("管理员账号已经初始化了");
        }
        //检查数据
        if (TextUtils.isEmpty(sobUser.getUserName())) {
            return ResponseResult.FAILED("用户名不能为空");
        }
        if (TextUtils.isEmpty(sobUser.getPassword())) {
            return ResponseResult.FAILED("密码不能为空");
        }
        if (TextUtils.isEmpty(sobUser.getEmail())) {
            return ResponseResult.FAILED("邮箱不能为空");
        }
        //补充数据
        sobUser.setId(String.valueOf(idWorker.nextId()));
        sobUser.setRoles(Constants.User.ROLE_ADMIN);
        sobUser.setAvatar(Constants.User.DEFAULT_AVATAR);
        sobUser.setState(Constants.User.DEFAULT_STATE);
        String remoteAddr = request.getRemoteAddr();
        String localAddr = request.getLocalAddr();
        //log.info(remoteAddr);
        //log.info(localAddr);
        sobUser.setLoginIp(remoteAddr);
        sobUser.setRegIp(remoteAddr);
        sobUser.setCreateTime(new Date());
        sobUser.setUpdateTime(new Date());
        //对密码进行加密
        //原密码
        String password = sobUser.getPassword();
        //加密
        String encode = bCryptPasswordEncoder.encode(password);
        sobUser.setPassword(encode);
        //保存到数据库
        userDao.save(sobUser);
        //更新已经添加的标记
        Setting setting = new Setting();
        setting.setId(idWorker.nextId() + "");
        setting.setKey(Constants.Settings.MANAGER_ACCOUNT_INIT_STATE);
        setting.setCreateTime(new Date());
        setting.setUpdateTime(new Date());
        setting.setValue("1");
        settingsDao.save(setting);
        return ResponseResult.SUCCESS("初始化成功");
    }

    @Autowired
    private Random random;
    @Autowired
    private RedisUtils redisUtils;
    public static final int[] captcha_font_types = {Captcha.FONT_1
            , Captcha.FONT_2
            , Captcha.FONT_3
            , Captcha.FONT_4
            , Captcha.FONT_5
            , Captcha.FONT_6
            , Captcha.FONT_7
            , Captcha.FONT_8
            , Captcha.FONT_9
            , Captcha.FONT_10};

    @Override
    public void createCaptcha(HttpServletResponse response) throws Exception {

        //防止重新创建，占用redis里太多资源
        //检查上一次id，如果有的话重复利用
        String lastId = CookieUtils.getCookie(getRequest(), Constants.User.LAST_CAPTCHA_ID);
        String key;
        if (TextUtils.isEmpty(lastId)) {
            key = idWorker.nextId() + "";
        } else {
            key = lastId;
        }

        //可以用了
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        int captchaType = random.nextInt(3);
        Captcha targetCaptcha;
        int width = 120;
        int height = 40;
        if (captchaType == 0) {
            // 三个参数分别为宽、高、位数
            targetCaptcha = new SpecCaptcha(width, height, 5);
        } else if (captchaType == 1) {
            //gif类型
            targetCaptcha = new GifCaptcha(width, height);
        } else {
            targetCaptcha = new ArithmeticCaptcha(width, height);
            targetCaptcha.setLen(2);  // 几位数运算，默认是两位
        }

        targetCaptcha.setFont(captcha_font_types[random.nextInt(captcha_font_types.length)]);
        // 设置类型，纯数字、纯字母、字母数字混合
        targetCaptcha.setCharType(Captcha.TYPE_ONLY_NUMBER);
        String content = targetCaptcha.text().toLowerCase();
        //保存到redis里
        //删除时机
        //1.自然过期
        //2.验证码用完以后删除
        //3.用完情况：看get的地方
        //把这个id写到cookie里，后面提交的时候，用于查询验证码的正确性
        CookieUtils.setUpCookie(response, Constants.User.LAST_CAPTCHA_ID, key);
        redisUtils.set(Constants.User.KEY_CAPTCHA_CONTENT + key, content, 60 * 10);
        // 输出图片流
        targetCaptcha.out(response.getOutputStream());
    }

    @Autowired
    private TaskService taskService;

    /**
     * 发送验证码
     * 使用场景：注册，找回密码，修改邮箱
     * 注册（register）：如果已经注册了，就提示，改邮箱已注册
     * 找回密码（forget）：如果没注册过，就提示改邮箱没有注册
     * 修改邮箱（update）：如果已经注册了，提示该邮箱已经注册
     *
     * @param request      1
     * @param emailAddress 1
     * @return
     */
    @Override
    public ResponseResult sendEmail(HttpServletRequest request, String type, String emailAddress, String captchaCode) {
        //检查人类验证码是否正确
        //从cookie里拿到key
        String captchaId = CookieUtils.getCookie(request, Constants.User.LAST_CAPTCHA_ID);
        String captchaValue = (String) redisUtils.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaId);
        if (!captchaCode.equals(captchaValue)) {
            return ResponseResult.FAILED("人类验证码不正确");
        }
        if (emailAddress == null) {
            return ResponseResult.FAILED("邮箱地址不能为空");
        }
        //根据类型。查询邮箱是否存在
        if ("register".equals(type) || "update".equals(type)) {
            SobUser userByEmail = userDao.findOneByEmail(emailAddress);
            if (userByEmail != null) {
                return ResponseResult.FAILED("该邮箱已注册");
            }
        } else if ("forget".equals(type)) {
            SobUser userByEmail = userDao.findOneByEmail(emailAddress);
            if (userByEmail == null) {
                return ResponseResult.FAILED("该邮箱未注册");
            }
        }
        //1.防止暴力发送：同一个邮箱，间隔要超过30秒，同一个ip,1小时最多发10次，如果是短信，最多5次
        String remoteAddr = request.getRemoteAddr();
        if (remoteAddr != null) {
            remoteAddr = remoteAddr.replaceAll(":", "_");
        }
        //log.info("sendEmail == > ip == > " + remoteAddr);
        //拿出来，如果有，那就过了
        String ipSendTimeValue = (String) redisUtils.get(Constants.User.KEY_EMAIL_SEND_IP + remoteAddr);
        Integer ipSpendTime;
        if (ipSendTimeValue != null) {

            ipSpendTime = Integer.parseInt(ipSendTimeValue);
        } else {
            ipSpendTime = 1;
        }
        if (ipSpendTime > 10) {
            return ResponseResult.FAILED("您发送验证码也太频繁了吧");
        }
        Object hasEmailSend = redisUtils.get(Constants.User.KEY_EMAIL_SEND_ADDRESS + emailAddress);
        if (hasEmailSend != null) {
            return ResponseResult.FAILED("您发送验证码也太频繁了吧");
        }
        //2.检查邮箱的地址是否正确
        boolean isEmailFormatOk = TextUtils.isEmailAddresOk(emailAddress);
        if (!isEmailFormatOk) {
            return ResponseResult.FAILED("邮箱格式不正确");
        }
        //3.发送验证码,6位数验证码。100000-999999
        //0-999999
        int code = random.nextInt(999999);
        if (code < 100000) {
            code += 100000;
        }
        try {
            taskService.sendEmailVerifyCode(String.valueOf(code), emailAddress);
        } catch (Exception e) {
            return ResponseResult.FAILED("发送验证码失败，请稍后再试");
        }


        //4.做记录
        //发送记录，code
        if (ipSpendTime == null) {
            ipSpendTime = 0;
        }
        ipSpendTime++;
        //1个小时有效期
        redisUtils.set(Constants.User.KEY_EMAIL_SEND_IP + remoteAddr, String.valueOf(ipSpendTime), 60 * 60);
        redisUtils.set(Constants.User.KEY_EMAIL_SEND_ADDRESS + emailAddress, "true", 30);
        //保存code,10分钟有效
        redisUtils.set(Constants.User.KEY_EMAIL_CODE_CONTENT + emailAddress, String.valueOf(code), 60 * 60);
        log.info("取出来的邮箱验证码值为" + redisUtils.get(Constants.User.KEY_EMAIL_CODE_CONTENT + emailAddress));
        return ResponseResult.SUCCESS("验证码发送成功");
    }

    @Override
    public ResponseResult register(SobUser sobUser, String emailCode, String captchaCode) {
        //第一步：检查当前用户名是否已经注册
        String userName = sobUser.getUserName();
        if (TextUtils.isEmpty(userName)) {
            return ResponseResult.FAILED("用户名不能为空");
        }
        SobUser userByName = userDao.findOneByUserName(userName);
        if (userByName != null) {
            return ResponseResult.FAILED("该用户已注册");
        }
        //第二步：检查邮箱格式是否正确
        String email = sobUser.getEmail();
        if (TextUtils.isEmpty(email)) {
            return ResponseResult.FAILED("邮箱地址不能为空");
        }
        if (!TextUtils.isEmailAddresOk(email)) {
            return ResponseResult.FAILED("邮箱格式不正确");
        }
        //第三步：检查该邮箱是否已经注册
        SobUser userByEmail = userDao.findOneByEmail(email);
        if (userByEmail != null) {
            return ResponseResult.FAILED("该邮箱地址已经注册过了");
        }

        //第四步：检查邮箱验证码是否正确
        String emailVerifyCode = (String) redisUtils.get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (TextUtils.isEmpty(emailVerifyCode)) {
            return ResponseResult.FAILED("邮箱验证码已过期");
        }
        if (!emailVerifyCode.equals(emailCode)) {
            return ResponseResult.FAILED("邮箱验证码不正确");
        } else {
            //正确，干掉redis的内容
            redisUtils.del(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        }
        //从redis里拿key
        String captchaKey = CookieUtils.getCookie(getRequest(), Constants.User.LAST_CAPTCHA_ID);
        if (TextUtils.isEmpty(captchaKey)) {
            return ResponseResult.FAILED("请允许保存cookies");
        }
        //第五步：检查图灵验证码是否正确
        String captchaVerifyCode = (String) redisUtils.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);

        if (TextUtils.isEmpty(captchaVerifyCode)) {
            return ResponseResult.FAILED("图片验证码已过期");
        }
        if (!captchaVerifyCode.equals(captchaCode)) {
            return ResponseResult.FAILED("验证码输入不正确");
        } else {
            //正确，干掉redis的内容
            redisUtils.del(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        }

        //达到可以注册的条件
        //第六步：对密码进行加密
        String password = sobUser.getPassword();
        if (TextUtils.isEmpty(password)) {
            return ResponseResult.FAILED("密码不能为空");
        }
        //第七步：不全数据
        sobUser.setPassword(bCryptPasswordEncoder.encode(sobUser.getPassword()));
        //包括：注册IP,登录IP，角色，头像，创建时间，更新时间
        String ipAddress = getRequest().getRemoteAddr();
        sobUser.setRegIp(ipAddress);
        sobUser.setLoginIp(ipAddress);
        sobUser.setUpdateTime(new Date());
        sobUser.setCreateTime(new Date());
        sobUser.setAvatar(Constants.User.DEFAULT_AVATAR);
        sobUser.setRoles(Constants.User.ROLE_NORMAL);
        sobUser.setState("1");
        sobUser.setId(idWorker.nextId() + "");
        //第八步：保存到数据库中
        userDao.save(sobUser);
        CookieUtils.deleteCookie(getResponse(), Constants.User.LAST_CAPTCHA_ID);
        //第九步：返回结果
        return ResponseResult.GET(ResponseState.JOIN_IN_SUCCESS);
    }

    @Override
    public ResponseResult doLogin(String captcha,
                                  SobUser sobUser, String from) {
        //from可能没有值
        //如果没有值，就给一个默认值
        if (TextUtils.isEmpty(from) || (!Constants.FROM_MOBILE.equals(from) &&
                !Constants.FROM_PC.equals(from))) {
            from = Constants.FROM_MOBILE;
        }
        HttpServletRequest request = getRequest();
        HttpServletResponse response = getResponse();
        String captchaKey = CookieUtils.getCookie(getRequest(), Constants.User.LAST_CAPTCHA_ID);
        String captchaValue = (String) redisUtils.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        if (!captcha.equals(captchaValue)) {
            return ResponseResult.FAILED("人类验证码不正确");
        }
        //验证成功，删除redis里的验证码
        redisUtils.del(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        //有可能是邮箱，也有可能是用户名
        String userName = sobUser.getUserName();
        if (TextUtils.isEmpty(userName)) {
            return ResponseResult.FAILED("账号不能为空");
        }
        String password = sobUser.getPassword();
        if (TextUtils.isEmpty(password)) {
            return ResponseResult.FAILED("密码不能为空");
        }
        SobUser userFromDb = userDao.findOneByUserName(userName);
        if (userFromDb == null) {
            userFromDb = userDao.findOneByEmail(userName);
        }
        if (userFromDb == null) {
            log.info("第一个");
            return ResponseResult.FAILED("用户名或密码不正确");
        }
        //用户存在
        //对比密码
        boolean matches = bCryptPasswordEncoder.matches(password, userFromDb.getPassword());
        if (!matches) {
            return ResponseResult.FAILED("用户名或密码不正确");
        }
//        if (!password.equals(userFromDb.getPassword())) {
//            return ResponseResult.FAILED("用户名或密码不正确");
//        }
        //密码是正确的
        //判断用户状态，如果是非正常状态，则返回结果
        if (!"1".equals(userFromDb.getState())) {
            return ResponseResult.ACCOUNT_DENIED();
        }
        //修改更新时间和登录IP
        userFromDb.setLoginIp(request.getRemoteAddr());
        userFromDb.setUpdateTime(new Date());
        createToken(response, userFromDb, from);
        CookieUtils.deleteCookie(getResponse(), Constants.User.LAST_CAPTCHA_ID);
        return ResponseResult.SUCCESS("登陆成功");
    }

    private String createToken(HttpServletResponse response, SobUser userFromDb, String from) {
        String oldTokenKey = CookieUtils.getCookie(getRequest(), Constants.User.COOKIE_TOKEN_KEY);

        //不能干掉了
        RefreshToken oldRefreshToken = refreshTokenDao.findOneByUserId(userFromDb.getId());
        //确保单端登录，删掉redis里的记录
        if (Constants.FROM_MOBILE.equals(from)) {
            //根据来源删除refreshToken中对应的token_key
            if (oldRefreshToken != null) {

                redisUtils.del(Constants.User.KEY_TOKEN + oldRefreshToken.getMobileTokenKey());
            }
            refreshTokenDao.deleteMobileTokenKey(oldTokenKey);
        } else if (Constants.FROM_PC.equals(from)) {
            //根据来源删除refreshToken中对应的tokenKey
            if (oldRefreshToken != null) {

                redisUtils.del(Constants.User.KEY_TOKEN + oldRefreshToken.getTokenKey());
            }
            refreshTokenDao.deletePcTokenKey(oldTokenKey);
        }
        //生成token，claims已经包含from了
        Map<String, Object> claims = CliamsUtils.sobUser2Claims(userFromDb, from);
        //token默认有效期2小时
        String token = JwtUtil.createToken(claims);
        //返货token的md5值，token会保存到redis里
        //前端访问时，携带token的md5key，从redis里获取即可
        String tokenKey = from + DigestUtils.md5DigestAsHex(token.getBytes());
        //保存token到redis里，有效期为两个小时。key的tokenKey
        redisUtils.set(Constants.User.KEY_TOKEN + tokenKey, token, Constants.TimeValueInSecond.HOUR_2);
        //把tokenKey写道cookies里
        //这个要动态获取，可以从request里获取
        CookieUtils.setUpCookie(response, Constants.User.COOKIE_TOKEN_KEY, tokenKey);
        //先判断数据库里有没有refreshToken
        //如果有就更新，如果没有就新创建
        RefreshToken refreshToken = refreshTokenDao.findOneByUserId(userFromDb.getId());
        if (refreshToken == null) {
            refreshToken = new RefreshToken();
            refreshToken.setId(idWorker.nextId() + "");
            refreshToken.setCreateTime(new Date());
            refreshToken.setUserId(userFromDb.getId());
        }
        //不管是过期了，还是新登录，都生成/更新refreshTo'ken
        //生成refreshToken
        String refreshTokenValue = JwtUtil.createRefreshToken(userFromDb.getId(), Constants.TimeValueInMillions.MONTH);
        //保存到数据库里
        //refreshToken tokenKey ,用户ID ，创建时间，更新时间
        refreshToken.setRefreshToken(refreshTokenValue);
        //要判断来源，如果是移动端就设置到移动端
        //如果时pc的就设置到默认
        if (Constants.FROM_PC.equals(from)) {
            refreshToken.setTokenKey(tokenKey);
        } else {
            refreshToken.setMobileTokenKey(tokenKey);
        }
        refreshToken.setUpdateTime(new Date());
        refreshTokenDao.save(refreshToken);
        return tokenKey;
    }


    /**
     * 本质就是通过携带的token——key来判断用户是否登录，如果登陆了就返回用户信息
     *
     * @return
     */

    @Override
    public SobUser  checkSobUser() {
        //拿到token_key
        String tokenKey = CookieUtils.getCookie(getRequest(), Constants.User.COOKIE_TOKEN_KEY);
        log.info("checkSobuser tokenKey == >" + tokenKey);
        if (TextUtils.isEmpty(tokenKey)) {
            return null;
        }
        //token中要解析出此请求俩字什么客户端
        SobUser sobUser = parseByTOkenKey(tokenKey);
        //比如说token过期了，被redis删掉了
        String from = tokenKey.startsWith(Constants.FROM_PC) ? Constants.FROM_PC : Constants.FROM_MOBILE;

        if (sobUser == null) {
            //说明解析出错了或者过期了
            //1.取mysql查询refreshToken
            //如果是PC，我们就以pc的token_key查
            //如果是mobile，我们就以mobile的token_key查
            RefreshToken refreshToken;
            if (Constants.FROM_PC.equals(from)) {
                refreshToken = refreshTokenDao.findOneByTokenKey(tokenKey);
            } else {
                refreshToken = refreshTokenDao.findOneByMobileTokenKey(tokenKey);
            }
            //2.如果不存在，就是当前访问没登陆，提示用户登录
            if (refreshToken == null) {
                log.info("refresh token is null ");
                return null;
            }
            //3.如果存在，就解析freshtoken
            try {
                //这个解析有可能出错，就是过期了
                //4.如果refreshtoken有效，创建新的token和freshtoken
                JwtUtil.parseJWT(refreshToken.getRefreshToken());
                String userId = refreshToken.getUserId();
                final SobUser userFromDb = userDao.findOneById(userId);

                //删掉refreshtoken的记录
                String newTokenKey = createToken(getResponse(), userFromDb, from);
                log.info("create new token and refresh token ... ");
                return parseByTOkenKey(newTokenKey);


            } catch (Exception e1) {
                //5.如果freshtoken过期了，提示用户登录
                log.info("refresh token is 过期了 ");
                return null;
            }
        }
        return sobUser;
    }

    @Override
    public ResponseResult getUserInfo(String userId) {
        //从数据库里获取
        SobUser user = userDao.findOneById(userId);
        //判断结果
        if (user == null) {
            //如果不存在，就返回不存在
            return ResponseResult.FAILED("用户不存在");

        }
        //如果存在，就复制对象，清空密码，email，登录ID，注册ip
        String userJson = gson.toJson(user);
        SobUser newSobUser = gson.fromJson(userJson, SobUser.class);
        newSobUser.setPassword("");
        newSobUser.setEmail("");
        newSobUser.setRegIp("");
        newSobUser.setLoginIp("");
        //返回结果
        return ResponseResult.SUCCESS("获取成功.").setData(newSobUser);
    }

    @Override
    public ResponseResult checkEmail(String email) {
        SobUser user = userDao.findOneByEmail(email);
        return user == null ? ResponseResult.FAILED("该邮箱未注册.") : ResponseResult.SUCCESS("该邮箱已经注册.");

    }

    @Override
    public ResponseResult checkUserName(String userName) {
        SobUser user = userDao.findOneByUserName(userName);
        return user == null ? ResponseResult.FAILED("该用户名可用.") : ResponseResult.SUCCESS("该用户名已存在.");


    }

    /**
     * 更新用户信息
     *
     * @param userId
     * @param sobUser
     * @return
     */
    @Override
    public ResponseResult updateUserInfo(String userId,
                                         SobUser sobUser) {
        //从token里解析出来的user，为了校验权限
        //只有用户才可以修改自己的信息
        SobUser userFromTokenKey = checkSobUser();
        if (userFromTokenKey == null) {
            return ResponseResult.ACCOUNT_NOT_LOGIN();
        }
        SobUser userFromDb = userDao.findOneById(userFromTokenKey.getId());
        //判断用户的ID是否一致，如果一直才可以修改
        if (!userFromDb.getId().equals(userId)) {
            return ResponseResult.PERMISSION_DENIED();
        }
        //可以进行修改
        //可以修改的项
        //用户名
        String userName = sobUser.getUserName();
        if (!TextUtils.isEmpty(userName) && !userName.equals(userFromTokenKey.getUserName())) {
            SobUser userByUserName = userDao.findOneByUserName(userName);
            if (userByUserName != null) {
                return ResponseResult.FAILED("该用户已注册");
            }
            userFromDb.setUserName(userName);
        }
        //头像
        if (!TextUtils.isEmpty(sobUser.getAvatar())) {
            userFromDb.setAvatar(sobUser.getAvatar());
        }
        userFromDb.setUpdateTime(new Date());
        //签名，可以为空
        userFromDb.setSign(sobUser.getSign());
        userDao.save(userFromDb);
        //干掉redis里的token，下一次请求，需要解析token的，就会根据refreshToken重新创建一个
        String tokenKey = CookieUtils.getCookie(getRequest(), Constants.User.COOKIE_TOKEN_KEY);
        redisUtils.del(Constants.User.KEY_TOKEN + tokenKey);
        return ResponseResult.SUCCESS("用户信息更改成功");
    }

    private HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return requestAttributes.getRequest();
    }

    private HttpServletResponse getResponse() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return requestAttributes.getResponse();
    }

    /**
     * 删除用户，并不是真正的删除
     * 而是修改权限
     * ps：需要管理员权限
     *
     * @param userId
     * @return
     */
    @Override
    public ResponseResult deleteUserById(String userId) {
        //可以删除用户了
        int result = userDao.deleteUserByState(userId);
        if (result > 0) {
            return ResponseResult.SUCCESS("删除成功");
        }
        return ResponseResult.FAILED("用户不存在");
    }

    /**
     * 需要管理员权限
     *
     * @param page
     * @param size
     * @return
     */
    @Autowired
    private UserNoPasswordDao userNoPasswordDao;

    @Override
    public ResponseResult listUsers(int page, int size, String userName, String email) {
        //判断角色
        //可以获取用户列表
        //分页查询
        //参数检查
        page = checkPage(page);
        size = checkSize(size);
        //根据注册日期来排序
        Sort sort = new Sort(Sort.Direction.DESC, "createTime");
        Pageable pageable = PageRequest.of(page - 1, size, sort);
        Page<SobUserNoPassword> all = userNoPasswordDao.findAll(new Specification<SobUserNoPassword>() {
            @Override
            public Predicate toPredicate(Root<SobUserNoPassword> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                //List<Predicate> predicates = new ArrayList<>();
                List<Predicate> predicates = new ArrayList<>();
                if (!TextUtils.isEmpty(userName)) {
                    Predicate preUser = cb.like(root.get("userName").as(String.class), "%" + userName + "%");
                    predicates.add(preUser);

                }

                if (!TextUtils.isEmpty(email)) {
                    Predicate preEmail = cb.equal(root.get("email").as(String.class), email);
                    predicates.add(preEmail);
                }

                Predicate[] preArray = new Predicate[predicates.size()];
                predicates.toArray(preArray);
                return cb.and(preArray);


            }
        }, pageable);
        return ResponseResult.SUCCESS("获取用户列表成功.").setData(all);
    }

    /**
     * 更新密码
     *
     * @param verifyCode
     * @param sobUser
     * @return
     */
    @Override
    public ResponseResult updateUserPassword(String verifyCode, SobUser sobUser) {
        //检查邮箱是否填写
        String email = sobUser.getEmail();
        if (TextUtils.isEmpty(email)) {
            return ResponseResult.FAILED("邮箱不能为空");
        }
        //根据邮箱取redis里那验证
        //进行对比
        String redisVerifyCode = (String) redisUtils.get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (redisVerifyCode == null || !redisVerifyCode.equals(verifyCode)) {
            return ResponseResult.FAILED("验证码错误");
        }
        redisUtils.del(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        int result = userDao.updatePasswordByEmail(bCryptPasswordEncoder.encode(sobUser.getPassword()), email);
        return result > 0 ? ResponseResult.SUCCESS("密码修改成功") : ResponseResult.FAILED("密码修改失败");
    }

    /**
     * 更新邮箱
     *
     * @param email
     * @param verifyCode
     * @return
     */
    @Override
    public ResponseResult updateEmail(String email, String verifyCode) {
        //1.确保已经登陆了
        SobUser sobUser = this.checkSobUser();
        //没有登陆
        if (sobUser == null) {
            return ResponseResult.ACCOUNT_NOT_LOGIN();
        }
        //2.确保新的邮箱是属于当前用户的
        String redisVerifyCode = (String) redisUtils.get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (TextUtils.isEmpty(redisVerifyCode) || !redisVerifyCode.equals(verifyCode)) {
            return ResponseResult.FAILED("验证码错误");
        }
        //验证码正确，删除验证码
        redisUtils.del(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        //可以修改邮箱
        int result = userDao.updateEmailById(email, sobUser.getId());
        return result > 0 ? ResponseResult.SUCCESS("邮箱修改成功") : ResponseResult.FAILED("邮箱修改失败");

    }

    @Override
    public ResponseResult doLogout() {
        //拿到token_key
        String tokenKey = CookieUtils.getCookie(getRequest(), Constants.User.COOKIE_TOKEN_KEY);
        if (TextUtils.isEmpty(tokenKey)) {
            return ResponseResult.ACCOUNT_NOT_LOGIN();
        }
        //删除redis里的token,因为各个客户端是相互独立的，所以可以删除
        redisUtils.del(Constants.User.KEY_TOKEN + tokenKey);
        //删除mysql里的refreshToken
        //这个不做删除了，制作更新
        //refreshTokenDao.deleteAllByTokenKey(tokenKey);
        if (Constants.FROM_PC.startsWith(tokenKey)) {
            refreshTokenDao.deletePcTokenKey(tokenKey);
        } else {
            refreshTokenDao.deleteMobileTokenKey(tokenKey);
        }

        //删除cookie里的token_key
        CookieUtils.deleteCookie(getResponse(), Constants.User.COOKIE_TOKEN_KEY);
        return ResponseResult.SUCCESS("退出登陆成功.");
    }

    @Override
    public ResponseResult getPcLoginQrCodeInfo() {
        //产生过hi去除上一次的loginId
        String lastLoginId = CookieUtils.getCookie(getRequest(), Constants.User.LAST_REQUEST_LOGIN_ID);
//        if (!TextUtils.isEmpty(lastLoginId)) {
//            //先把redis里的删除
//            redisUtils.del(Constants.User.KEY_PC_LOGIN_ID + lastLoginId);
//            //检查上次的请求时间，如果太频繁，则直接骂人
//            Object lastGetTime = redisUtils.get(Constants.User.LAST_REQUEST_LOGIN_ID + lastLoginId);
//            if (lastGetTime != null) {
//                return ResponseResult.FAILED("服务器繁忙，请稍后重试");
//            }
//        }
        //1.生成一个唯一的ID
        long code;
        if (!TextUtils.isEmpty(lastLoginId)) {
            code = Long.parseLong(lastLoginId);
        } else {

            code = idWorker.nextId();
        }
        //2.保存到redis里，值为false，时间为5分钟（二维码有效期）
        redisUtils.set(Constants.User.KEY_PC_LOGIN_ID + code,
                Constants.User.KEY_PC_LOGIN_STATE_FALSE,
                Constants.TimeValueInSecond.MIN_5);
        Map<String, Object> result = new HashMap<>();
        String originalDomain = TextUtils.getDomain(getRequest());
        result.put("code", String.valueOf(code));
        result.put("url", originalDomain + "/portal/image/qr-code/" + code);
        CookieUtils.setUpCookie(getResponse(), Constants.User.LAST_REQUEST_LOGIN_ID, String.valueOf(code));
        //redisUtils.set(Constants.User.LAST_REQUEST_LOGIN_ID + String.valueOf(code), "true", Constants.TimeValueInSecond.SECOND_10);

        //返回结果
        return ResponseResult.SUCCESS("获取成功").setData(result);
    }

    @Autowired
    private CountDownLatchManager countDownLatchManager;

    /**
     * 检查二维码的登陆状态
     * 结果有：
     * 1.登陆成功（loginId对应的值为有id内容）
     * 2.等待扫描（loginId对应的值为false）
     * 3.二维码已经过期了loginId对应的值为null
     * <p>
     * <p>
     * <p>
     * 是被pc端轮询调用的
     *
     * @param loginId
     * @return
     */

    @Override
    public ResponseResult CheckQrCodeLoginState(String loginId) {
        //从redis里取出来
        ResponseResult result = checkLoginIdState(loginId);
        if (result != null) return result;
        //先等待一段时间，再去检查
        //如果超出这个时间，我们就返回等待扫码
        Callable<ResponseResult> callable = new Callable<ResponseResult>() {
            @Override
            public ResponseResult call() throws Exception {
                try {
                    log.info("start waiting for scan...");
                    //先阻塞
                    countDownLatchManager.getLatch(loginId).await(Constants.User.QR_CODE_STATE_CHECK_WAITING_TIME,
                            TimeUnit.SECONDS);
                    //收到状态更新的通知，我们就检查loginId对应的状态
                    log.info("start check log in state...");
                    ResponseResult checkResult = checkLoginIdState(loginId);
                    if (checkResult != null) return checkResult;
                    //超时则返回等待扫描
                    //完事后，删除对应的latch
                    return ResponseResult.WAITING_FOR_SCAN();
                } finally {
                    log.info("delete latch...");
                    countDownLatchManager.deleteLatch(loginId);
                    {

                    }
                }

            }
        };
        try {
            return callable.call();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResponseResult.WAITING_FOR_SCAN();
    }

    /**
     * 更新二维码的登陆状态
     *
     * @param loginId
     * @return
     */
    @Override
    public ResponseResult updateQrCodeLoginState(String loginId) {
        //1.检查用户是否登录
        SobUser sobUser = checkSobUser();
        if (sobUser == null) {
            return ResponseResult.ACCOUNT_NOT_LOGIN();
        }

        //2.改变loginId的对应值=true
        redisUtils.set(Constants.User.KEY_PC_LOGIN_ID + loginId, sobUser.getId());
        //2.1 通知正在等待的扫描任务
        countDownLatchManager.onPhoneDoLogin(loginId);
        //3.返回结果
        return ResponseResult.SUCCESS("登陆成功");
    }

    @Override
    public ResponseResult parseToken() {
        SobUser sobUser = checkSobUser();
        if (sobUser == null) {
            return ResponseResult.FAILED("用户未登录.");
        }
        return ResponseResult.SUCCESS("获取用户列表成功.").setData(sobUser);
    }

    @Override
    public ResponseResult resetPassword(String userId, String password) {

        //查询出用户来
        SobUser user = userDao.findOneById(userId);
        //判断是否存在
        if (user == null) {
            return ResponseResult.FAILED("用户不存在.");
        }
        //密码进行加密
        user.setPassword(bCryptPasswordEncoder.encode(password));
        //处理结果
        userDao.save(user);
        return ResponseResult.SUCCESS("密码重置成功");

    }

    @Override
    public ResponseResult getRegisterCount() {

        long count = userDao.count();
        return ResponseResult.SUCCESS("注册用户总数获取成功").setData(count);
    }

    @Override
    public ResponseResult checkEmailCode(String email, String emailCode, String captchaCode) {
        //检查人类验证码
        String captchaId = CookieUtils.getCookie(getRequest(), Constants.User.LAST_CAPTCHA_ID);
        String captcha = (String) redisUtils.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaId);
        if (!captchaCode.equals(captcha)) {
            return ResponseResult.FAILED("图灵验证码不正确");
        }
        //检查邮箱code
        String redisVerifyCode = (String) redisUtils.get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (!emailCode.equals(redisVerifyCode)) {
            return ResponseResult.FAILED("邮箱证码不正确");
        }
        return ResponseResult.SUCCESS("验证码正确");
    }

    private ResponseResult checkLoginIdState(String loginId) {
        String loginState = (String) redisUtils.get(Constants.User.KEY_PC_LOGIN_ID + loginId);
        if (loginState == null) {
            //二维码过期
            return ResponseResult.QR_CODE_DEPRECATE();
        }

        //部位false，且状态部位null，那么就是用户的id了，也就是登陆成功了

        if (!TextUtils.isEmpty(loginState) && !Constants.User.KEY_PC_LOGIN_STATE_FALSE.equals(loginState)) {
            //创建token，也就是走PC端的登录
            SobUser userFromDb = userDao.findOneById(loginState);
            if (userFromDb == null) {
                return ResponseResult.QR_CODE_DEPRECATE();
            }
            createToken(getResponse(), userFromDb, Constants.FROM_PC);
            CookieUtils.deleteCookie(getResponse(), Constants.User.LAST_REQUEST_LOGIN_ID);
            //登陆成功
            return ResponseResult.LOGIN_SUCCESS();
        }
        return null;
    }

    /**
     * 解析此token时pc还是手机端
     *
     * @param tokenKey
     * @return
     */

    private String parseFrom(String tokenKey) {
        String token = (String) redisUtils.get(Constants.User.KEY_TOKEN + tokenKey);

        if (token != null) {
            try {
                Claims claims = JwtUtil.parseJWT(token);
                return CliamsUtils.getFrom(claims);

            } catch (Exception e) {
                log.info("parseByTOkenKey == 》" + tokenKey + "过期了");

            }
        }
        return null;
    }


    private SobUser parseByTOkenKey(String tokenKey) {
        String token = (String) redisUtils.get(Constants.User.KEY_TOKEN + tokenKey);
        if (token != null) {
            try {
                Claims claims = JwtUtil.parseJWT(token);
                return CliamsUtils.claims2Sobuser(claims);

            } catch (Exception e) {
                log.info("parseByTOkenKey == 》" + tokenKey + "过期了");
                return null;
            }
        }
        return null;
    }
}
